using System;
using System.Collections.Generic;
using System.Text;
using Barrage.Global;
using Barrage.Global.Vertex;
using SlimDX.Direct3D9;
using SlimDX;

namespace Barrage.Meshes
{
    /// <summary>
    /// XMesh implements the Mesh class to render meshes loaded from a DirectX .x file.
    /// It also generates materials from the loaded file
    /// </summary>
    public class XMesh:Barrage.Mesh
    {
        BoundingSphere boundingSphere;

        SlimDX.Direct3D9.Mesh mesh = null;

        /// <summary>
        /// Creates the bounding sphere associated to a mesh
        /// </summary>
        /// <param name="mesh">The DirectX mesh</param>
        /// <returns>A bounding sphere surrounding al vertices of the mesh</returns>
        public static BoundingSphere CreateBoundingSphere(SlimDX.Direct3D9.Mesh mesh)
        {
            DataStream stream = mesh.LockVertexBuffer(LockFlags.ReadOnly);

            byte[] buffer = new byte[stream.Length];
            stream.Read(buffer, 0, (int)stream.Length);

            mesh.UnlockVertexBuffer();


            Vector3[] vertices = new Vector3[mesh.VertexCount];
            int j = 0;
            for (int i = 0; i < buffer.Length; i += mesh.BytesPerVertex)
            {
                Vector3 v = new Vector3();
                v.X = BitConverter.ToSingle(buffer, i);
                v.Y = BitConverter.ToSingle(buffer, i + sizeof(float));
                v.Z = BitConverter.ToSingle(buffer, i + 2 * sizeof(float));
                vertices[j++] = v;
            }

            return BoundingSphere.FromPoints(vertices);
                
        }

        /// <summary>
        /// Constructor from a DirectX mesh
        /// </summary>
        /// <param name="mesh">The DirectX mesh</param>
        public XMesh(SlimDX.Direct3D9.Mesh mesh)
        {
            this.mesh = mesh;
            mesh.OptimizeInPlace(MeshOptimizeFlags.Compact | MeshOptimizeFlags.VertexCache);

            boundingSphere = CreateBoundingSphere(mesh);
            
            foreach (ExtendedMaterial m in mesh.GetMaterials())
            {
                materials.Add(new Material(m));
                
            }
            mesh.SetMaterials(null);
            mesh.SetEffects(null);            
        }

        /// <summary>
        /// Constructor from a .x file path
        /// </summary>
        /// <param name="filename">The .x file path</param>
        public XMesh(string filename) : this(SlimDX.Direct3D9.Mesh.FromFile(Engine.Device, filename, MeshFlags.Managed)) {
        }

        /// <summary>
        /// \internal Renders the mesh if the bounding sphere passes the cull check
        /// </summary>
        /// <param name="item">Item that is being rendered</param>
        public override void Render(Barrage.Item item)
        {
            RenderStats.RenderCalls++;

            Vector3 scale;
            Quaternion rotation;
            Vector3 translation;
            item.Motion.WorldMatrix.Decompose(out scale, out rotation, out translation);

            BoundingSphere bb=boundingSphere;
            bb.Center+=translation;
            bb.Radius*=Math.Max(scale.X,Math.Max(scale.Y,scale.Z));
            if (Scene.CurrentInstance.Camera.CheckCull(bb))
            {
                NLog.LogManager.GetCurrentClassLogger().Debug("Culling Mesh");
                RenderStats.CulledObjects++;
                return;
            }
            
            int i=0;
            
            foreach(Material m in materials){
                shader.BeginRender(item, m);
                int passNumber = shader.PassNumber(m);
                for (int j = 0; j < passNumber; j++)
                {
                    shader.BeginPass(j);
                    mesh.DrawSubset(i);
                    shader.EndPass();
                }
                shader.EndRender();
                i++;
            }
            
            NLog.LogManager.GetLogger("Barrage::Scene::Meshes::XMesh").Debug("Rendering Mesh");
        }

        /// <summary>
        /// \internal Free resources
        /// </summary>
        public override void Dispose()
        {
            if (mesh!=null)
                mesh.Dispose();
            mesh = null;
            base.Dispose();
        }
    }
}
